package edu.imac.angryfood.utils;

import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.SpriteSheet;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import edu.imac.angryfood.component.AbstractProjectile;
import edu.imac.angryfood.component.AcceleratedProjectile;
import edu.imac.angryfood.component.BombProjectile;
import edu.imac.angryfood.component.BoomerangProjectile;
import edu.imac.angryfood.component.DividedProjectile;
import edu.imac.angryfood.component.SimpleProjectile;
import edu.imac.angryfood.shapes.Circle;
import edu.imac.angryfood.shapes.Polygon;
import edu.imac.angryfood.shapes.Rectangle;
import edu.imac.angryfood.shapes.Shape;
import edu.imac.angryfood.world.Body;
import edu.imac.angryfood.world.World;


public class ParserXML {
	
	public static class Material{
		private String name;
		private float density;
		private float friction;
		private float restitution;
		private int life;

		/**
		 * 
		 * @param name Name of this material
		 * @param density Density of this material
		 * @param friction Friction of this material
		 * @param restitution Restitution of this material
		 * @param life Number of life point of this material
		 */
		public Material(String name, float density, float friction, float restitution, int life){
			this.name = name;
			this.density = density;
			this.friction = friction;
			this.restitution = restitution;
			this.life = life;
		}
		
		public String getName(){ return name; }
		public float getDensity(){ return density; }
		public float getFriction(){ return friction ; }
		public float getRestitution(){ return restitution ;}
		public int getLife(){ return life; }
		
		/**
		 * Return a specific Material from the list according to the name (String material)
		 * @param matList list of Materials
		 * @param material name of the sought material 
		 * @return the sought Material
		 */
		static public Material getMaterialOnList(ArrayList<Material> matList, String material){
			Material mat = null;
			for(int i=0; i<matList.size(); i++){
				if(matList.get(i).getName().equals(material)){
					mat = matList.get(i);
					break;
				}
			}
			return mat;
		}
	}
	
	
	/**
	 * Return a list of Materials read the xmlFile
	 * @param xmlFile name of the xml file (xml of the level description)
	 */
	static public ArrayList<Material> ReadMaterialStyle(String xmlFile){
		ArrayList<Material> materials = new ArrayList<Material>();
		
		try {
			File file = new File(xmlFile);
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			DocumentBuilder db = dbf.newDocumentBuilder();
			Document doc = db.parse(file);
			doc.getDocumentElement().normalize();
			NodeList blockList = doc.getElementsByTagName("material");
			
			for (int s = 0; s < blockList.getLength(); s++) {
				
				Node fstNode = blockList.item(s);
					if (fstNode.getNodeType() == Node.ELEMENT_NODE) {
					Element fstElmnt = (Element) fstNode;
					Attr fstNm = fstElmnt.getAttributeNode("name");
					String name = fstNm.getNodeValue();
					float density = getNextValueFloat(fstElmnt, "density");
					float friction = getNextValueFloat(fstElmnt, "friction");
					float restitution = getNextValueFloat(fstElmnt, "restitution");
					int life = getNextValueInt(fstElmnt, "life");
					
					Material mat = new Material(name, density, friction, restitution, life);
					materials.add(mat);
				}
			}
			
			
			} catch (Exception e) {
			    e.printStackTrace();
		}
		
		return materials;
	}
	
	
	

	static public class BlockStyle{
		private String name;
		/** Material of the block **/
		private Material material;
		/** true if the block is static **/
		private boolean isStatic;
		/** Shape of the block (rectangle, circle or polygon) **/
		private Shape shape;
		/** Source of the image (spriteSheet of the block) **/
		private String srcImage;
		/** width of the part to cut in the SpriteSheet **/
		private int widthImage;
		/** width of the part to cut in the SpriteSheet **/
		private int heightImage;
		/** size of the separation between two images in the SpriteSheet **/
		private int separationImage;
		/** number of states in the SpriteSheet **/
		private int nbStates;
		/** the SpriteSheet generated thanks to all previous parameters  **/
		private SpriteSheet images;
		
		/**
		 * 
		 * @param name name of the new Block
		 * @param matList list of Material of the game
		 * @param material name of the Material of the new Block
		 * @param isStatic true if the block is a static block, false if is not
		 * @param shape shape type of the block
		 * @param img source of the spriteSheet image
		 * @param imgW width of a part to cut in the SpriteSheet
		 * @param imgH height of a part to cut in the SpriteSheet
		 * @param separation size of the separation between two images in the SpriteSheet
		 * @param nbStates number of states in the SpriteSheet 
		 */
		public BlockStyle(String name, ArrayList<Material> matList, String material, boolean isStatic, Shape shape, String img, int imgW, int imgH, int separation, int nbStates){
			this.name = name;
			this.material = Material.getMaterialOnList(matList, material);
			this.isStatic = isStatic;
			this.shape = shape;
			try{ 
				Image imgSpSheet = new Image("res/img/game/"+img).getFlippedCopy(false,true);
				this.images = new SpriteSheet(imgSpSheet, imgW , imgH, separation);
			}catch(SlickException e){ System.out.println(e); } 
			this.srcImage= "res/img/game/"+img;
			this.widthImage = imgW;
			this.heightImage = imgH;
			this.separationImage = separation;
			this.nbStates = nbStates;
		}
		
		public String getName(){ return name; }
		public Shape getShape(){ return shape; }
		public Boolean getIsStatic(){ return isStatic; }
		public Material getMaterial(){ return material; }
		public SpriteSheet getSpriteSheet(){ return images; }
		public String getSRCImage(){return srcImage; }
		public int getWidthImage(){ return widthImage; }
		public int getHeightImage(){ return heightImage; }
		public int getSeparationImage(){ return separationImage; }
		public int getNbStates(){ return nbStates; }

		
		/**
		 * Return a specific BlockStyle from the list styleList according to the name (String nomStyle)
		 * @param styleList list of BlockStyle
		 * @param nomStyle name of the sought BlockStyle 
		 * @return the sought BlockStyle
		 */
		static public BlockStyle getBlockStyleOnList(ArrayList<BlockStyle> styleList, String nomStyle){
			BlockStyle result = null;
			for(int i=0; i<styleList.size(); i++){
				if(styleList.get(i).getName().equals(nomStyle)){
					result = styleList.get(i);
					break;
				}
			}
			return result;
		}
	}
	
	
	
	/**
	 * Return a list of BlockStyle read the xmlFile
	 * @param xmlFile name of the xml file (xml of the level description)
	 * @param matList list of Material previously read in the xmlFile
	 * @return list of BlockStyle
	 */
	static public ArrayList<BlockStyle> ReadBlockStyle(String xmlFile, ArrayList<Material> matList){
		ArrayList<BlockStyle> ListblockStyle = new ArrayList<BlockStyle>();
		
		try {
			File file = new File(xmlFile);
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			DocumentBuilder db = dbf.newDocumentBuilder();
			Document doc = db.parse(file);
			doc.getDocumentElement().normalize();
			NodeList blockList = doc.getElementsByTagName("block");
			
			for (int s = 0; s < blockList.getLength(); s++) {
				
				Node fstNode = blockList.item(s);
				if (fstNode.getNodeType() == Node.ELEMENT_NODE) {
					Element fstElmnt = (Element) fstNode;
					Attr fstNm = fstElmnt.getAttributeNode("name");
					String name = fstNm.getNodeValue();
					fstNm = fstElmnt.getAttributeNode("material");
					String material = fstNm.getNodeValue();
					fstNm = fstElmnt.getAttributeNode("static");
					String staticString = fstNm.getNodeValue();
					boolean isStatic = false;
					if(staticString.equals("true"))
						isStatic = true;

					NodeList shapeDef = doc.getElementsByTagName("shape");
					Node shapeNode = shapeDef.item(s);
					Element shapeElement = (Element) shapeNode;
					fstNm = shapeElement.getAttributeNode("shapeType");
					String shapeType = fstNm.getNodeValue();
					Shape shape = null;
					if(shapeType.equals("RECTANGLE")){
						float width = getNextValueFloat(shapeElement, "width");
						float height = getNextValueFloat(shapeElement, "height");
						shape = (Shape)new Rectangle(width, height);
					}
					else if(shapeType.equals("CIRCLE")){
						float radius = getNextValueFloat(shapeElement, "radius");
						shape = (Shape)new Circle(radius);
					}
					else if(shapeType.equals("POLYGON")){
						NodeList verticesList = shapeElement.getElementsByTagName("vertice");
						Polygon poly = new Polygon();
						for (int v = 0; v < verticesList.getLength(); v++) {
							Element verticeElmnt = (Element)verticesList.item(v);
							float x = Float.parseFloat(verticeElmnt.getAttributeNode("x").getNodeValue());
							float y = Float.parseFloat(verticeElmnt.getAttributeNode("y").getNodeValue());
							poly.addPoint(x, y);
						}
						shape = poly;
					}
					// image :

					NodeList imageDef = doc.getElementsByTagName("image");
					Node imageNode = imageDef.item(s);
					Element imageElement = (Element) imageNode;
					fstNm = imageElement.getAttributeNode("width");
					int width = Integer.parseInt(fstNm.getNodeValue());
					fstNm = imageElement.getAttributeNode("height");
					int height = Integer.parseInt(fstNm.getNodeValue());
					fstNm = imageElement.getAttributeNode("separation");
					int separation = Integer.parseInt(fstNm.getNodeValue());
					fstNm = imageElement.getAttributeNode("nbEtats");
					int nbEtats = Integer.parseInt(fstNm.getNodeValue());
					String img = getNextValueString(imageElement, "src");
					
					BlockStyle block = new BlockStyle(name, matList, material, isStatic, shape, img, width, height, separation, nbEtats);
					ListblockStyle.add(block);
					
				}
			}
			
			
			} catch (Exception e) {
			    e.printStackTrace();
		}
		
		return ListblockStyle;
	}
	
	/**
	 * Read the list of block in the xml of the level, create blocks and add them to the word. This function use Material and BlockStyle list previously generated.
	 * @param blocksStyle list of BlocksStyle to use to create blocks of the level
	 * @param blockList list of block definition in the xml file
	 * @world world of the game
	 */
	static private void readBlock(ArrayList<BlockStyle> blocksStyle, NodeList blockList, World world){
		for (int s = 0; s < blockList.getLength(); s++) {
			Node fstNode = blockList.item(s);
			if (fstNode.getNodeType() == Node.ELEMENT_NODE) {
				Element fstElmnt = (Element) fstNode;
				Attr fstNm = fstElmnt.getAttributeNode("name");
				String name = fstNm.getNodeValue();
				fstNm = fstElmnt.getAttributeNode("x");
				float x = Float.parseFloat(fstNm.getNodeValue());
				fstNm = fstElmnt.getAttributeNode("y");
				float y = Float.parseFloat(fstNm.getNodeValue());
				fstNm = fstElmnt.getAttributeNode("rotate");
				int rotate = Integer.parseInt(fstNm.getNodeValue());
				
				BlockStyle newBlock = BlockStyle.getBlockStyleOnList(blocksStyle, name);
				
				Shape shape = newBlock.getShape();
				Body body = new Body(shape, x, y, newBlock.getIsStatic(), newBlock.getSpriteSheet());
				body.setDensity(newBlock.getMaterial().getDensity());
				body.setRestitution(newBlock.getMaterial() .getRestitution());
				body.setFriction(newBlock.getMaterial().getFriction());
				body.setLife(newBlock.getMaterial().getLife());
				world.add(body);
				body.setRotation((float)(rotate*Math.PI/180));
				if(newBlock.getIsStatic()){
	                   body.setType("STATIC");
				}else{
	                   body.setType("BLOCK");
				}
	            if(newBlock.getMaterial().getName().equals("STRONG_TARGET") || newBlock.getMaterial().getName().equals("NORMAL_TARGET") || newBlock.getMaterial().getName().equals("WEAK_TARGET"))
	            {	
	            	body.setType("TARGET");
	            }
			}
		}
	}
	
	/**
	 * Read the list of projectiles in the xml of the level, create and add them to the word.
	 * @param blocksStyle list of BlocksStyle to use to create blocks of the level
	 * @param blockList list of block definition in the xml file
	 * @world world of the game
	 */
	static private void readProjectiles(ArrayList<BlockStyle> blocksStyle, NodeList projList, World world){
		int radius = 0, width = 0, height = 0;
		try{
			for (int p = 0; p < projList.getLength(); p++) {
				AbstractProjectile.nbProj++;
				Node fstNode = projList.item(p);
				if (fstNode.getNodeType() == Node.ELEMENT_NODE) {
					Element fstElmnt = (Element) fstNode;
					String type = fstElmnt.getAttributeNode("type").getNodeValue();
					
					String shapeType = fstElmnt.getAttributeNode("shape").getNodeValue();
					if(shapeType.equals("circ"))
						radius = Integer.parseInt(fstElmnt.getAttributeNode("radius").getNodeValue());
					else if(shapeType.equals("rect")){
						width = Integer.parseInt(fstElmnt.getAttributeNode("width").getNodeValue());
						height = Integer.parseInt(fstElmnt.getAttributeNode("height").getNodeValue());
					}
					int num = Integer.parseInt(fstElmnt.getAttributeNode("num").getNodeValue());
					String image = fstElmnt.getAttributeNode("image").getNodeValue();
					int first;
					if (num==0){
						first=0;
					}
					else {
						first=1;
					}
					int offsetY = 62;
					int x = AbstractProjectile.xInitXML-first*(100+(num-1)*50);
					int y = AbstractProjectile.yInitXML-(offsetY*first);
					if(type.equals("SIMPLE")){
						if(shapeType.equals("circ"))
							world.add(new SimpleProjectile(x,y,radius,false, num, image));
						else if(shapeType.equals("rect"))
							world.add(new SimpleProjectile(x,y,width, height,false, num, image));
					}
					if(type.equals("BOMB")){
						if(shapeType.equals("circ"))
							world.add(new BombProjectile(x,y,radius,false, num, image));
						else if(shapeType.equals("rect"))
							world.add(new BombProjectile(x,y,width, height,false, num, image));
					}
					if(type.equals("DIVIDED")){
						ArrayList<String> listImagesName = new ArrayList<String>();
						listImagesName.add(fstElmnt.getAttributeNode("image1").getNodeValue());
						listImagesName.add(fstElmnt.getAttributeNode("image2").getNodeValue());
						listImagesName.add(fstElmnt.getAttributeNode("image3").getNodeValue());
						if(shapeType.equals("circ"))
							world.add(new DividedProjectile(x,y,radius,false, num, image, listImagesName));
						else if(shapeType.equals("rect")){
							int widthMini = Integer.parseInt(fstElmnt.getAttributeNode("widthMini").getNodeValue());
							int heightMini = Integer.parseInt(fstElmnt.getAttributeNode("heightMini").getNodeValue());
							world.add(new DividedProjectile(x,y,width, height,false, num, image, widthMini, heightMini, listImagesName));
						}
							
					}
					if(type.equals("BOOMERANG")){
						if(shapeType.equals("circ"))
							world.add(new BoomerangProjectile(x,y,radius,false, num, image));
						else if(shapeType.equals("rect"))
							world.add(new BoomerangProjectile(x,y,width, height,false, num, image));
					}
					if(type.equals("ACCELERATED")){
						if(shapeType.equals("circ"))
							world.add(new AcceleratedProjectile(x,y,radius,false, num, image));
						else if(shapeType.equals("rect"))
							world.add(new AcceleratedProjectile(x,y,width, height,false, num, image));
					}
				}
			}
		}catch (Exception e) {e.printStackTrace();};		
		
	}
	
	/**
	 * Read the informations about the floot in the xml of the level, create the block and add it to the word. 
	 * @param floorData list of informations about the floor in the xml file
	 * @world world of the game
	 */
	static public void readFloor(NodeList floorData, World world){
		try{
			Element fstElmnt = (Element)floorData.item(0);
			Attr fstNm = fstElmnt.getAttributeNode("type");
			String type = fstNm.getNodeValue();
			fstNm = fstElmnt.getAttributeNode("friction");
			float friction = Float.parseFloat(fstNm.getNodeValue());
			fstNm = fstElmnt.getAttributeNode("restitution");
			float restitution = Float.parseFloat(fstNm.getNodeValue());
			int width = getNextValueInt(fstElmnt, "width");
			int height = getNextValueInt(fstElmnt, "height");
			String image = getNextValueString(fstElmnt, "image");
			float positionYBotImg = getNextValueFloat(fstElmnt, "positionYBotImg");
			
			Image floorImg = new Image("res/img/game/"+image).getFlippedCopy(false, true);
			Body floor = null;
			if(type.equals("rectangle"))
				floor = new Body(new Rectangle((float)width, (float)height), (width/2)-400-250, positionYBotImg, true, floorImg);
			floor.setRestitution(restitution);
			floor.setFriction(friction);
			world.add(floor);
			
			
			
		} catch(Exception e){e.printStackTrace();}
	}
	
	/**
	 * Read the xml of the level to create block, projectiles, target and floor and add the to the world. This function use Material and BlockStyle list previously generated.
	 * @param xmlFile name of the xml file
	 * @param blocksStyle list of BlocksStyle to use to create blocks of the level (linked to the Material list)
	 * @param world of the game
	 * @param background background of the level - init in this function
	 */
	static public void XMLLevel(String xmlFile, ArrayList<BlockStyle> blocksStyle, World world ){
		try {
			File file = new File(xmlFile);
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			DocumentBuilder db = dbf.newDocumentBuilder();
			Document doc = db.parse(file);
			doc.getDocumentElement().normalize();
			

			Element fstElmnt = (Element)doc.getElementsByTagName("map").item(0);
			Attr fstNm = fstElmnt.getAttributeNode("slingShotX");
			String slingShotX = fstNm.getNodeValue();
			fstNm = fstElmnt.getAttributeNode("slingShotY");
			String slingShotY = fstNm.getNodeValue();
			AbstractProjectile.xInitXML=Integer.parseInt(slingShotX);
			AbstractProjectile.yInitXML=Integer.parseInt(slingShotY);
			
			fstNm = fstElmnt.getAttributeNode("backgroundImage");
			String imgBackground = fstNm.getNodeValue();
			world.backgroundImage 	= new Image("res/img/game/"+imgBackground).getFlippedCopy(false, true);
			
			fstNm = fstElmnt.getAttributeNode("skyImage");
			String skyImage = fstNm.getNodeValue();
			world.skyImage 	= new Image("res/img/game/"+skyImage).getFlippedCopy(false, true);
			
			// Parsin floot
			NodeList floorData = doc.getElementsByTagName("floor");
			readFloor(floorData, world);
			
			// Parsing blocks
			NodeList blockList = doc.getElementsByTagName("block");
			readBlock(blocksStyle, blockList, world);
			
			// Parsing projectiles
			NodeList projList = doc.getElementsByTagName("projectile");
			readProjectiles(blocksStyle, projList, world);
			
		} catch (Exception e) {
			    e.printStackTrace();
		}
			  
	}
	
	
	/**
	 * Get the next int value on the xml
	 * @param fstElmnt xml element 
	 * @param search the sought string
	 * @return the sought int value
	 */
	public static int getNextValueInt(Element fstElmnt, String search){
		NodeList fstNmElmntLst = fstElmnt.getElementsByTagName(search);
	    Element fstNmElmnt = (Element) fstNmElmntLst.item(0);
	    NodeList fstNm = fstNmElmnt.getChildNodes();
	    int result = Integer.parseInt(((Node) fstNm.item(0)).getNodeValue());
	    return result;
	}
	
	/**
	 * Get the next float value on the xml
	 * @param fstElmnt xml element 
	 * @param search the sought string
	 * @return the sought float value
	 */
	public static float getNextValueFloat(Element fstElmnt, String search){
		NodeList fstNmElmntLst = fstElmnt.getElementsByTagName(search);
	    Element fstNmElmnt = (Element) fstNmElmntLst.item(0);
	    NodeList fstNm = fstNmElmnt.getChildNodes();
	    float result = Float.parseFloat(((Node) fstNm.item(0)).getNodeValue());
	    return result;
	}
	
	/**
	 * Get the next String value on the xml
	 * @param fstElmnt xml element 
	 * @param search the sought string
	 * @return the sought String value
	 */
	public static String getNextValueString(Element fstElmnt, String search){
		NodeList fstNmElmntLst = fstElmnt.getElementsByTagName(search);
	    Element fstNmElmnt = (Element) fstNmElmntLst.item(0);
	    NodeList fstNm = fstNmElmnt.getChildNodes();
	    String result = ((Node) fstNm.item(0)).getNodeValue();
	    return result;
	}
	
	
	public static void updateScoreIfBest(String fileName, int newScore){
		try{
			SAXBuilder sxb = new SAXBuilder();
			org.jdom.Document document = sxb.build(new File("res/levels/"+fileName));
			org.jdom.Element racine = document.getRootElement();

			int highscore = Integer.parseInt(racine.getAttribute("highscore").getValue());
			
			if(highscore < newScore){
				System.out.println("highscore : "+highscore+" VS score = "+newScore);
				racine.setAttribute("highscore", ""+newScore);
				highscore = Integer.parseInt(racine.getAttribute("highscore").getValue());
				XMLOutputter sortie = new XMLOutputter(Format.getPrettyFormat());
				sortie.output(document, new FileOutputStream("res/levels/"+fileName));
			}
		} catch (Exception e) {
			    e.printStackTrace();
		}
	}
}